package emergence.game.level;

import java.awt.Point;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;

import emergence.entities.Entity;
import emergence.game.Game;
import emergence.game.Tickable;
import emergence.game.gfx.Colors;
import emergence.game.gfx.Font;
import emergence.game.gfx.Screen;
import emergence.game.level.tiles.Tile;
import emergence.resource.ResourceInputFile;
import emergence.resource.Resources;
import util.Logger;
import util.Nifty;

/**
 * Represents a Level, or chunk of the map.
 *
 * @author Nathan T
 *
 */
public class Level implements Tickable {

    private final Logger logger = Logger.getDefaultLogger();

    // State Variables
    private LevelMap levelMap;
    private EntityMap entityMap;
    private int width;
    private int height;
    public final int TILE_SIZE;
    private String name;
    private String filePath;
    
    private boolean renderable = true;

    public final static Tile VOID = Resources.getTile("VOID");

    public Level(int width, int height, int tileSize) {
        this.width = width;
        this.height = height;
        this.TILE_SIZE = tileSize;
        this.generateDefaultLevel();
    }

    public Level(String name, LevelMap map, EntityMap entityMap, int tileSize, String filePath) {
        this.name = name;
        this.width = map.width();
        this.height = map.height();
        this.TILE_SIZE = tileSize;
        this.levelMap = map;
        this.entityMap = entityMap;
        this.filePath = filePath;
    }

    private void generateDefaultLevel() {
        Tile[][] tiles = new Tile[width][height];
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                if (((x % 10) == 5) || ((y % 10) == 5)) {
                    tiles[x][y] = Resources.getTile("GRASS_ANIM");
                }
                else if (x * y % 10 < 7) {
                    tiles[x][y] = Resources.getTile("GRASS");
                }
                else {
                    tiles[x][y] = Resources.getTile("STONE");
                }
            }
        }

        for (int i = 10; i < 20; i++) {
            for (int j = 10; j < 20; j++) {
                tiles[i][j] = Resources.getTile("WATER");
            }
        }
        this.levelMap = new LevelMap(tiles, null);
        entityMap = new EntityMap(new Entity[this.width][this.height]);
    }

    public void render(Screen screen, int xOffset, int yOffset) {
        // Avoid rendering void tiles if possible
        if (xOffset < 0) {
            xOffset = 0;
        }
        if (xOffset > ((width << 4) - screen.screenWidth)) {
            xOffset = (width << 4) - screen.screenWidth;
        }

        if (yOffset < 0) {
            yOffset = 0;
        }
        if (yOffset > ((height << 4) - screen.screenHeight)) {
            yOffset = (height << 4) - screen.screenHeight;
        }

        // Center the screen
        screen.setOffset(xOffset, yOffset);

        // Render Tiles
        for (int i = (xOffset / TILE_SIZE); i < ((xOffset + screen.screenWidth) / TILE_SIZE) + 1; i++) {
            for (int j = (yOffset / TILE_SIZE); j < ((yOffset + screen.screenHeight) / TILE_SIZE) + 1; j++) {
                screen.render(i * TILE_SIZE, j * TILE_SIZE, getTile(i, j), 1);
            }
        }

        // Render Entities
        for (int i = (xOffset / TILE_SIZE); i < ((xOffset + screen.screenWidth) / TILE_SIZE) + 1; i++) {
            for (int j = (yOffset / TILE_SIZE); j < ((yOffset + screen.screenHeight) / TILE_SIZE) + 1; j++) {
                Entity ent = entityAt(new Point(i, j));
                if (ent != null) {
                    screen.render((i * TILE_SIZE) + ent.xOffset(), (j * TILE_SIZE) + ent.yOffset(), entityAt(new Point(i, j)), 1);
                }
            }
        }

        Font.render(Game.lastFrames + "", screen, xOffset + TILE_SIZE * 36,
                yOffset, Colors.get(-1, -1, -1, 000), 1);
    }

    public Tile getTile(int x, int y) {
        return levelMap.tileAt(x, y);
    }

    public void setTile(int x, int y, Tile tile) {
        levelMap.setTile(x, y, tile);
    }

    public Entity entityAt(Point point) {
        return entityMap.entityAt(point);
    }

    @Override
    public void tick() {
        entityMap.tick();

        Tile.tickTiles();
    }

    public void addEntity(Entity entity, Point point) {
        entity.setCoords(point);
        entityMap.addEntity(entity, point);
    }

    public boolean moveEntity(Point oldCoords, Point newCoords) {
        if (entityMap.entityAt(newCoords) != null) {
            return false;
        }
        entityMap.moveEntityTo(oldCoords, newCoords);
        return true;
    }

    public void removeEntity(Point point) {
        entityMap.addEntity(null, point);
    }
    
    public int width() {
        return width;
    }

    public int height() {
        return height;
    }
    
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public String getFilePath() {
        return filePath;
    }
    
    public String getRelativeFilepath(String path) {
        String folderPath = ResourceInputFile.getFolderFrom(filePath);
        int indexOfFolder = filePath.indexOf(folderPath);
        return path.substring(indexOfFolder + folderPath.length());
    }

    public String[] getContent() {
        ArrayList<String> cont = new ArrayList<>();

        cont.add("NAME");
        cont.add("{");
        cont.add("name = " + name);
        cont.add("}");

        cont.add("LEVEL_MAP");
        cont.add("{");
        cont.add("file = " + getRelativeFilepath(levelMap.filePath()));
        cont.add("}");

        cont.add("ENTITY_MAP");
        cont.add("{");
        cont.add("file = " + getRelativeFilepath(entityMap.filePath()));
        cont.add("}");

        String[] results = new String[cont.size()];
        for (int i = 0; i < results.length; i++) {
            results[i] = cont.get(i);
        }

        return results;
    }

    public void save(String filePath) {
        try {
            levelMap.setFilePath(filePath + "\\levelmap.cfg");
            levelMap.save();
            entityMap.setFilePath(filePath + "\\entitymap.cfg");
            entityMap.save();

            File outputFile = new File(filePath + "\\level.cfg");
            try (FileWriter fw = new FileWriter(outputFile);
                    PrintWriter pw = new PrintWriter(fw);) {

                String[] content = getContent();
                for (String line : content) {
                    pw.println(line);
                }
            }
        }
        catch (IOException e) {
            logger.log(e);
        }
    }

    public void save() {
        save(Nifty.directoryFrom(this.filePath));
    }

    public void applyResize(LevelResize resize) {
        renderable = false;
        
        int oldWidth = this.width;
        int oldHeight = this.height;

        this.width = oldWidth + resize.getAddLeft() + resize.getAddRight() - resize.getRemLeft() - resize.getRemRight();
        this.height = oldHeight + resize.getAddTop() + resize.getAddBot() - resize.getRemTop() - resize.getRemBot();

        levelMap.applyResize(resize);
        entityMap.applyResize(resize);
        
        renderable = true;
    }
    
    public boolean isRenderable() {
        return renderable;
    }

    public static class LevelResize {

        private int addTop;
        private int addBot;
        private int addLeft;
        private int addRight;
        private int remTop;
        private int remBot;
        private int remLeft;
        private int remRight;

        public LevelResize() {
            addTop = 0;
            addBot = 0;
            addRight = 0;
            addLeft = 0;
            remTop = 0;
            remBot = 0;
            remLeft = 0;
            remRight = 0;
        }

        /**
         * @return the addTop
         */
        public int getAddTop() {
            return addTop;
        }

        /**
         * @param addTop the addTop to set
         */
        public void setAddTop(int addTop) {
            this.addTop = addTop;
        }

        /**
         * @return the addBot
         */
        public int getAddBot() {
            return addBot;
        }

        /**
         * @param addBot the addBot to set
         */
        public void setAddBot(int addBot) {
            this.addBot = addBot;
        }

        /**
         * @return the addLeft
         */
        public int getAddLeft() {
            return addLeft;
        }

        /**
         * @param addLeft the addLeft to set
         */
        public void setAddLeft(int addLeft) {
            this.addLeft = addLeft;
        }

        /**
         * @return the addRight
         */
        public int getAddRight() {
            return addRight;
        }

        /**
         * @param addRight the addRight to set
         */
        public void setAddRight(int addRight) {
            this.addRight = addRight;
        }

        /**
         * @return the remTop
         */
        public int getRemTop() {
            return remTop;
        }

        /**
         * @param remTop the remTop to set
         */
        public void setRemTop(int remTop) {
            this.remTop = remTop;
        }

        /**
         * @return the remBot
         */
        public int getRemBot() {
            return remBot;
        }

        /**
         * @param remBot the remBot to set
         */
        public void setRemBot(int remBot) {
            this.remBot = remBot;
        }

        /**
         * @return the remLeft
         */
        public int getRemLeft() {
            return remLeft;
        }

        /**
         * @param remLeft the remLeft to set
         */
        public void setRemLeft(int remLeft) {
            this.remLeft = remLeft;
        }

        /**
         * @return the remRight
         */
        public int getRemRight() {
            return remRight;
        }

        /**
         * @param remRight the remRight to set
         */
        public void setRemRight(int remRight) {
            this.remRight = remRight;
        }
    }

}
